local dbg       =   require "jass.debug"
local slk       =   require "jass.slk"
local runtime   =   require "jass.runtime"
local message   =   require "jass.message"
local flagcov   =   require "util.flag"
local japi2     =   require "util.japi2"
local symbols   =   require "symbol.ability"
local handle_manager = handle_manager_class:create()
local sync_manager = handle_manager_class:create()

local skill = {
    __tostring = function(self) 
		if self.owner then 
        	return ("[%s] 的 skill[%s]"):format(tostring(self.owner), self.name) 
		else 
        	return ("skill类[%s]"):format(self.name) 
		end
    end,
    __call = function(self, data) 
		for k,v in pairs(data) do 
			self[k] = v 
		end
		return self
    end,
} 
setmetatable(skill, skill)

skill.dbgcount = 0

skill.allskills = {}
skill.skillsmap = {}

-- 目标类型
skill.target_flag = {
    ["没有"]  = 0,
    ["物体"] = 1,
    ["点"] = 2,
    ["点&物体"] = 3,
}

-- 目标允许
skill.allow_flag = ac.unit.allow_flag

-- 技能类结构
local mt = {}
skill.__index = mt 

--[skill]
mt.type = "skill"

--[技能状态] active|passive
mt.skill_type = "active"

--[图标]
mt.art = "ReplaceableTextures\\CommandButtons\\BTNReturnGoods.blp"

--[提示]
mt.tip = "技能"

--[描述]
mt.ubertip = ""

--[脚注]
mt.description = nil 

--[热键]
mt.hotkey = nil

--[等级]
mt.level = 1

--[等级上限]
mt.max_level = 1

--[释放范围]
mt.range = 0.00

--[区域范围]
mt.area = 0.00

--[冷却时间]
mt.cool = 0.00

--[充能时间]
mt.charge_cool = 0

--[魔法消耗]
mt.cost = 0

--[释放动画]
mt.animation = "spell"

--[释放时间]
mt.casttime = 0.15

--[释放硬直]
mt.caststiff = false 

--[目标允许]
mt.allowtype = "地面"

--[目标类型] 没有|物体|点|点&物体
mt.targettype = "没有"

--[科技需求] 搜 科技限制例子.lua 
mt.requires = nil 

--技能位置
mt.slotid = 1

--[是一个面板上的技能] false 则是个纯lua对象的抽象概念技能 没有按钮
mt.is_ability = false 

--[选择圈] 会根据技能的 area 显示一个圆形选择圈
mt.is_selector = false

--[享受冷却缩减] 部分技能可能不需要享受冷却缩减效果
mt.cooling_flag = false

--[重新添加不会刷新CD]
mt.keep_cool = true

--[物理技能] 不会受到沉默效果限制
mt.physics = false

--[不打断指令]
mt.resume_flag = false

--[顺发指令]
mt.instant_flag = false

--[永久技能] 变身后仍然存在
mt.permanent = true 

--[获取ID]
function mt:get_id()
    return self.name 
end 

--[被动]
function mt:is_passive()
    return self.skill_type == "passive"
end

--[主动]
function mt:is_active()
    return self.skill_type ~= "passive"
end 

-- 创建施法表
function mt:create_cast(data)
	local self = self.parent_skill or self
	local skill = data or {}
    for k,v in pairs(self.data or {}) do
        skill[k] = format_obj(self,v,self.level)
    end
	skill.is_cast_flag = true
	skill.parent_skill = self
	setmetatable(skill, {__index = self})
	return skill
end

--[获取slk数据]
function mt:get_slk(name, default)
    if self._id == 0 then 
        return default
    end 
    local id = FourCC(self._id)
    local obj = slk.ability[id]
    if not obj then
        log.error("技能数据未找到", id , name)
        return default
    end
    local data = obj[name]
    if not data then
        return default
    end
    if type(default) == "number" then
        return tonumber(data) or default
    end
    return data
end

--[执行事件]
function mt:notify(name, ...)
    local func = self[name]
    if func then 
        return select(2, xpcall(func, runtime.error_handle, self, ...))
    end 
end 

--[获得技能句柄(jass)]
function mt:get_handle()
    if self.removed or not self.has_ability then
        return jass.ConvertUnitState(0)
    end
    return japi.EXGetUnitAbility(self.owner.handle, FourCC(self._id))
end

--[获取所有者]
function mt:get_owner()
    return self.owner
end 

--[设置技能自定义值]
function mt:set_data(key,value)
    self.user_data = self.user_data or {}
    self.user_data[key] = value 
end

--[获取技能自定义值]
function mt:get_data(key)
    return self.user_data and self.user_data[key] or nil
end

--[获取技能图标]
function mt:get_art()
    if self:is_passive() then 
        return japi.GetPassiveIcon(self.art) 
    end 
    return self.art 
end

--[获取暗图标路径]
function mt:get_dis_art()
    if not self.disabled_art then 
        self.disabled_art = japi.GetDisabledIcon( self.art )
    end 
    return self.disabled_art
end 

--[设置技能图标]
function mt:set_art(art)
    self.art = art 
    if self.has_ability then 
        japi.EXSetAbilityDataString(self:get_handle(), 1, 0xCC, self:get_art() )
    end 
end

--获取技能的热键
function mt:get_hotkey()
	return self.hotkey or (self._id and self:get_slk('Hotkey', ''))
end

--[获取作用范围]
function mt:get_area()
    if self.has_ability then 
        return japi.EXGetAbilityDataReal(self:get_handle(), 1, 0x6A)
    end 
    return self.area 
end

--[设置作用范围]
function mt:set_area(area)
    self.area = area 
    if self.has_ability then 
        japi.EXSetAbilityDataReal(self:get_handle(), 1, 0x6A, area)
    end 
end

--[获取法力消耗]
function mt:get_cost()
    -- if self.has_ability then 
    --     return japi.EXGetAbilityDataInteger(self:get_handle(), 1, 0x68)
    -- end 
    return self.cost 
end

--[设置法力消耗]
function mt:set_cost(cost)
    self.cost = cost 
    if self.has_ability then 
        japi.EXSetAbilityDataInteger( self:get_handle() , 1, 0x68 ,cost)
    end 
end 

--[获取施法范围]
function mt:get_range()
    if self.has_ability then 
        return japi.EXGetAbilityDataReal( self:get_handle() , 1, 0x6B )
    end 
    return self.range 
end

--[设置施法范围]
function mt:set_range(range)
    self.range = range 
    if self.has_ability then 
        japi.EXSetAbilityDataReal( self:get_handle(), 1, 0x6B, range )
    end 
end 

--[获取冷却时间]
function mt:get_cool()
    if self.has_ability then 
        return japi.EXGetAbilityDataReal(self:get_handle(), 1, 0x69 )
    end 
    return self.cool 
end

--[设置冷却时间]
function mt:set_cool(cool)
    self.cool = cool 
    if self.has_ability then 
        japi.EXSetAbilityDataReal( self:get_handle() , 1, 0x69 ,cool)
    end 
end 

--获取充能
mt.cooldown_mode = 0
mt.spell_stack = 0
mt.charge_max_stack = 0
function mt:get_stack()
	return self.spell_stack
end

--增加充能
function mt:add_stack(n)
	self:set_stack(self.spell_stack + n)
end

--设置充能
function mt:set_stack(n)
	self.spell_stack = n
	if self.cooldown_mode == 1 then
		if self.spell_stack < self.charge_max_stack then
			if self:get_charge_cd() == 0 then
                self:set_charge_cd(self.charge_cool)
            end
		else
			if self:get_charge_cd() > 0 then
                self:set_charge_cd(0)
            end
		end
	end
end

--[设置释放期间硬直]
function mt:set_stiff(flag)
    self.cast_stiff = (flag == true)
    if self.has_ability then 
        japi.EXSetAbilityDataReal( self:get_handle(), 1, 0x70, self.cast_stiff and 1 or 0)
    end
end 

--[技能处于冷却]
function mt:is_cooling()
    return self:get_cast_cd() > 0
end

--[获取技能剩余冷却]
function mt:get_cast_cd()
    if not self._cooling then 
        return 0 
    end
    return math.max(self._cooling - ac.clock() / 1000 ,0)
end

--获取充能剩余时间
function mt:get_charge_cd()
	if not self._cooling_charge then
		return 0
	end
    return math.max(self._cooling_charge - ac.clock() / 1000 ,0)
end


function mt:get_cd()
    return self:get_cast_cd()
end

--获取技能最大冷却
function mt:get_max_cast_cd()
	if not self.owner or self.cooling_flag then
		return self.cool
	end

	local cd = (self.cool or 0) * (1 - self.owner:get '冷却缩减' / 100)

	cd = math.max(0,cd)
	return cd
end

--获取充能最大冷却
function mt:get_max_charge_cd()
	if not self.owner or self.cooling_flag then
		return self.charge_cool
	end
	return (self.charge_cool or 0) * (1 - self.owner:get '冷却缩减' / 100)
end

--[设置技能冷却]
function mt:set_cast_cd(cool)
    local max_cool = self:get_max_cast_cd()
    cool = (cool or self.cool)
    self._cooling = ac.clock() / 1000 + cool

    self._remaing = math.min(self.cool, cool or self.cool)
    
	if self.cast_cd_timer then
		self.cast_cd_timer:remove()
		self.cast_cd_timer = nil
	end

    self.cast_cd_timer = ac.wait(self._remaing*1000,function()
        self:notify('on_cooldown')
    end)


    if self.keep_cool then 
        local unit = self.owner 
        local name = self:get_id()
        unit.skill_cool_data[name] = {
            _cooling = self._cooling,
            _remaing = self._remaing,
        }
    end
    if self._away_cooling == 0 or self._away_cooling == 1 then 
        self._away_cooling = 1
        return 
    end 
    self:set_show_cd()
    
end

--设置技能冷却
--	充能冷却时间
function mt:set_charge_cd(cool)
    cool = (cool or self.charge_cool) 
    self._cooling_charge = ac.clock() / 1000 + cool
    
	if self.charge_cd_timer then
		self.charge_cd_timer:remove()
		self.charge_cd_timer = nil
	end

	local function cooled()
		if self.spell_stack < self.charge_max_stack then
			self:add_stack(1)
		end
	end

    if cool>0 then
        self.charge_cd_timer = ac.wait(cool*1000,cooled)
    else
        cooled()
    end

    self:set_show_cd()
end

function mt:set_cd(cool)
	if self.cooldown_mode == 0 then
		self:set_cast_cd(cool)
		return
	end
	if self.cooldown_mode == 1 then
		self:set_charge_cd(cool)
		return
	end
end

function mt:get_max_cd()
	if self.cooldown_mode == 0 then
		return self:get_max_cast_cd()
	end
	if self.cooldown_mode == 1 then
		return self:get_max_charge_cd()
	end
	return 0
end

--显示冷却时间
function mt:get_show_cd()
	local cool = self:get_cast_cd()
	local max_cool = self:get_max_cast_cd()
    --充能
	if self.cooldown_mode == 1 and self.spell_stack < 1 then
		local charge_cool = self:get_charge_cd()
		local max_charge_cool = self:get_max_charge_cd()
        return charge_cool, max_charge_cool
	end
	return cool, max_cool
end

function mt:set_show_cd()
	local cool, max_cool = self:get_show_cd()
    if self.has_ability then 
		japi.EXSetAbilityDataReal(self:get_handle(), 1, 0x69, max_cool)
		japi.EXSetAbilityState(self:get_handle(), 0x01, cool)
		--japi.EXSetAbilityDataReal(self:get_handle(), 1, 0x69, 0)
    end 
end


--[使技能进入冷却]
function mt:active_cd()
	self:set_cd(self:get_max_cd())
end

function mt:cooling()
	self:set_cd(self:get_max_cd())
end


--启动技能冷却(施法流程)
function mt:start_cd_by_cast(cool)
	if self.cooldown_mode == 1 then
		self:add_stack(-1)
	end
	self:set_cast_cd(max_cd)
end

-- 刷新技能显示
function mt:refresh()
    if self.has_ability then 
        jass.SetUnitAbilityLevel( self.owner.handle, FourCC(self._id), 2 )
        self:set_title(self.title or self.name)
        self:set_tip(self.tip)
        japi.EXSetAbilityDataReal( self:get_handle(), 1, 0x6E, self.is_selector and 3 or 1)
        jass.SetUnitAbilityLevel( self.owner.handle, FourCC(self._id), 1 )
        ac.console:notify("控制台-刷新技能显示")
    end 
end

-- 设置标题
function mt:set_title(title)
    self.title = title
    if self:is_active() then 
        local hotkey = self:get_hotkey()
        if hotkey and hotkey~='' then 
            self.title_ui = title .. ("(|cffffcc00%s|r)"):format(hotkey)
        else
            self.title_ui = title
        end 
    else
        if self.max_level > 1 then 
            if self.level < self.max_level then 
                self.title_ui = title .. (" - |cffc0c0c0等级%s|r"):format(self.level)
            else 
                self.title_ui = title .. (" - |cffc0c0c0等级Max|r")
            end 
        else
            self.title_ui = title .. (" - |cffc0c0c0[被动]|r")
        end 
    end 
end 

-- 获取标题
function mt:get_title()
    return format_obj(self, self.title or self.name)
end


-- 设置脚注
function mt:set_description(description)
    self.description = description
end 

-- 获取脚注
function mt:get_description()
    return self.description
end

-- 设置描述
function mt:set_tip(ubertip)
    self.tip = ubertip
    self.tip_ui = format_obj(self, self.tip)
end 

-- 获取描述
function mt:get_tip()
    if self.is_shop_item then
        local tip1 = self.sell_tip
        local tip = tip1 or self.tip
        return format_obj(self, tip)
    end
    local tip = self.tip
    return format_obj(self, tip)
end


--是否满级
function mt:is_max()
    return (self:get_level() == self.max_level)
end

-- 获取等级
function mt:get_level()
    return self.level 
end

-- 设置等级
function mt:set_level(level)
    local levelo = self.level
    local leveln =  math.min(level, self.max_level) 
    self.level = leveln 
    self:notify("on_level_change", levelo, leveln)
    self:refresh()
end 

-- 提升等级
function mt:add_level(lv)
    self:set_level(self.level + (lv or 1))
end

-- 修改数据
function mt:change(name, newvalue)
    local oldvalue = self[name]
    if not oldvalue then 
        return false
    end 
    self[name] = newvalue 
    self:notify("on_change",name, newvalue, oldvalue)
    self:refresh()
end

-- 判断技能基础释放条件
function mt:cast_check(is_cast)
    local u = self.owner 
    if self.removed then 
        return false 
    end 
    if not self.is_ability and not is_cast then 
        return false 
    end 
    if not self.has_ability and not is_cast then 
        return false 
    end 
    if self:is_passive() then 
        return false 
    end 
    if self:is_silent() then 
        return false 
    end
    if self:is_cooling() then 
        return false 
    end 
    if (u:has_restriction "失控") and not self.physics then
        return false
    end
    if (u:has_restriction "晕眩") and not self.physics then
        return false
    end
    if ((u:has_restriction "沉默") and not self.physics) or u:has_restriction "禁魔" then
        return false
    end
    if u:is_dead() then 
        return false 
    end 
    if u:is_hidden() then 
        return false 
    end 
    if u:is_loaded() then 
        return false 
    end 
	if self.cooldown_mode == 1 and self.spell_stack < 1 then
		return false
	end
    if (u:get('魔法') < self:get_cost()) then 
        return false 
    end
    return true
end

-- 发动技能
function mt:cast_by_client(target)
    local result = false
    if self:cast_check() then 
        if self.targettype == "没有" then 
            result = self.owner:issue_order(self:get_order())
        else
            result = self.owner:issue_order(self:get_order(), target)
        end
    end
    return result 
end 

-- 发动技能逻辑（不走魔兽流程)
function mt:cast(target)
    local hero = self.owner
    if self:cast_check(true) then
        self.target = target
        hero:add('魔法',-self:get_cost())
        self:notify("on_cast_start", target)
        self:notify("on_cast_ready", target)
        self:notify("on_cast_shot", target)
        self:notify("on_cast_finish", target)
        self:notify("on_cast_stop", target)
        local cooldown = self:get_max_cast_cd()
        if self.away_cooling == 1 then 
            cooldown = self:get_cast_cd()
        end 
        self:start_cd_by_cast(cooldown)
    end
end 



function mt:register_sync_id() 
    if not self.syncid then 
        local syncid = sync_manager:allocate()
        self.syncid = syncid
        skill.skillsmap[syncid] = self
    end 
end 

function mt:flush_sync_id() 
    if self.syncid then 
        local syncid = self.syncid 
        skill.skillsmap[syncid] = nil
    end 
end 

-- 本地发动技能 只能放无目标技能
function mt:client_cast()
    if self.targettype == "没有" and self:cast_check() and self.syncid then 
        local h, t = (("%04d"):format(self.syncid)):match("(%d%d)(%d%d)")
        if self.physics and self.owner:has_restriction "晕眩" then 
            ac.message.sync("客户端-释放技能", tonumber(h), tonumber(t), ac.message.convert_flag("瞬发|独立|交互|恢复"))
        else 
            ac.message.sync("客户端-释放技能", tonumber(h), tonumber(t), ac.message.convert_flag("独立|交互|恢复"))
        end 
        return true
    end
    return false 
end 

--[获取技能命令]
function mt:get_order() 
    return self:get_slk("order","channel")
end

mt.silent_count = 0

--[判断技能处于沉默状态]
function mt:is_silent()
    return self.silent_count > 0 
end 

--[设置技能沉默]
function mt:set_silent(flag)
    if self.has_ability then 
        local unit = self:get_owner()
        local handle = self:get_handle()
        local order = unit:current_order()
        if flag then 
            self.silent_count = self.silent_count + 1
            if self.silent_count == 1 then
                jass.SetUnitAbilityLevel( unit.handle, FourCC(self._id), 2 )
                japi.EXSetAbilityDataString( handle, 1, 0xCC, self:get_dis_art() )
                japi.EXSetAbilityDataReal( handle, 1, 0x6D, 0 )
                jass.SetUnitAbilityLevel( unit.handle, FourCC(self._id), 1 )
                if order and order == self:get_order() then 
                    unit:issue_order("move", unit:get_point())
                    unit:issue_order("stop")
                end 
            end
        else 
            self.silent_count = self.silent_count - 1
            if self.silent_count == 0 then
                jass.SetUnitAbilityLevel( unit.handle, FourCC(self._id), 2 )
                japi.EXSetAbilityDataString( handle, 1, 0xCC, self:get_art() )
                japi.EXSetAbilityDataReal( handle, 1, 0x6D, flagcov.tobit(self.targettype, skill.target_flag) )
                jass.SetUnitAbilityLevel( unit.handle, FourCC(self._id), 1 )
            end
        end 
    end 
end 

mt.enable_count = 0

--[是否启用]
function mt:is_enable()
	return self.enable_count == 0
end

--[设置启用]
function mt:set_enable(flag)
    if flag then 
        self.enable_count = self.enable_count - 1
        if not self.removed and self.added and self.enable_count == 0 then
            self:set_silent(false)
            self:refresh()
        end
    else 
        self.enable_count = self.enable_count + 1
        if not self.removed and self.added and self.enable_count == 1 then
            self:set_silent(true)
            self:refresh()
        end
    end 
end

--[添加]
function mt:add()
    if self.added then 
        return 
    end 
    self.added = true 

    skill.dbgcount = skill.dbgcount + 1

    local name = self.name 
    local unit = self.owner 

    local skills = unit.skills 
    local handle = handle_manager:allocate()
    self.handle = handle 
    self.gchash = handle 

    dbg.gchash(self, handle) 

    unit.skills[self] = true 
    skill.allskills[handle] = self
    
    if not unit.skill_cool_data then 
        unit.skill_cool_data = { }
    end
    
	if self.cooldown_mode == 1 then
		self.spell_stack = self.charge_max_stack
	end

    skill.init_skill(self)
    self:set_level(self.level)
    self:set_title(self.title or self.name)
    self:set_tip(self.tip)

    if self.requires then 
        local player = unit:get_owner()
        local filter = ac.filter.parser(self.requires)
        if filter then
            if filter:check(player.upgrades) == false then  
                self:set_enable(false)
                self.requires_ui = filter:format(player.upgrades)
                self.upgrade_change = true
            end
            self.upgrade_filter = filter
            self.upgrade_trigger = player:on "玩家-科技等级变化" (function (t, player, name)
                if self.upgrade_filter:reference(name) then
                    if self.upgrade_filter:check(player.upgrades) then 
                        if self.upgrade_change then 
                            self:set_enable(true)
                            self.upgrade_change = false
                        end
                        self.requires_ui = nil
                    else
                        if not self.upgrade_change then 
                            self:set_enable(false)
                            self.upgrade_change = true 
                        end
                        self.requires_ui = filter:format(player.upgrades)
                    end 
                    ac.console:notify("控制台-刷新技能显示")
                end 
            end)
        end
    end 
    
    local cooldown = self:get_cast_cd()
    if self.keep_cool then 
        local cool_name = self:get_id() 
        local cool_data = self.owner.skill_cool_data[cool_name]
        if cool_data then 
            cooldown = math.max(cool_data._cooling - (ac.clock() / 1000 ) ,0)
        end 
    end 
    self:set_cast_cd(cooldown)

    if (unit:has_restriction "沉默" and not self.physics) or (unit:has_restriction "禁魔") then 
        self:set_silent(true)
    end
    
    self:notify("on_add",unit)
    
    unit:notify("单位-添加技能",unit,self)

    return self
end 

--[技能删除]
function mt:remove()
    if self.removed then 
        return 
    end
    self.removed = true 

    local unit = self.owner

    self:notify("on_remove",unit)

    unit:notify("单位-即将移除技能", unit, self)
	self:gc_clear()
    
    local handle = self.handle 
    handle_manager:free(self.handle)

    if self.upgrade_trigger then 
        self.upgrade_trigger:remove()
    end
    if self.animation_timer then 
        self.animation_timer:remove()
    end 
    if self.cast_cd_timer then 
        self.cast_cd_timer:remove()
    end 
    

    skill.allskills[handle] = nil
    unit.skills[self] = nil  

    -- 如果有技能则判断
    if self.has_ability then
        table.insert(symbols[self.slotid], self._id)
        self:remove_ability()
    end 

    skill.dbgcount = skill.dbgcount - 1
end 


--[添加技能]
function mt:add_ability()
	if self._id and self.is_ability then
        if not self.owner.play_skills then 
            self.owner.play_skills = {}
        end 
        self.owner:add_ability(self._id)
        self.order = slk.ability[self._id]["Order"]
        self.owner.used_slot_list[self.slotid] = self
        self.owner.play_skills[FourCC(self._id)] = self
        self.owner.play_skills[self.order] = self
        self.has_ability = true 
        local cooldown = self:get_cast_cd()
        if self.keep_cool then 
            local cool_name = self:get_id() 
            local cool_data = self.owner.skill_cool_data[cool_name]
            if cool_data then 
                cooldown = math.max(cool_data._cooling - (ac.clock() / 1000 ) ,0)
            end 
        end 
        self:set_cast_cd(cooldown)
        -- if self.skill_type == "passive" then 
        --     japi2.SilentUnitAbility( self.owner.handle, FourCC(self._id), true )
        -- end 
        if self.instant_flag then 
            self:register_sync_id()
        end
	end
end

--[移除技能]
function mt:remove_ability()
	if self._id and self.is_ability then
        if self.resume_flag and self.owner:current_order() == self:get_order() then 
            self.owner:add_restriction "晕眩"
            self.owner:remove_ability(self._id)
            self.owner:remove_restriction "晕眩"
        else 
            self.owner:remove_ability(self._id)
        end 
        self.owner.used_slot_list[self.slotid] = nil
        self.owner.play_skills[FourCC(self._id)] = nil
        self.owner.play_skills[self.order] = nil
        self.has_ability = false
        self:flush_sync_id()
	end
end

function mt:gc(target)
	--目前只支持两种自动回收
	--1为function，即属性
	--2为带有remove方法的对象，如特效，计时器，触发
    local skill = self.parent_skill or self
	if not skill._gc_target then
		skill._gc_target = {}
	end
	table.insert(skill._gc_target,target)
end

function mt:gc_clear()
    local skill = self.parent_skill or self
	if skill._gc_target then
		for k,v in ipairs(skill._gc_target) do
			--function针对属性，调用后还原
			if type(v) == 'function' then
				v()
			else
				--remove针对计时器、触发、特效等，调用后删除
				v:remove()
			end
		end
		skill._gc_target = nil
	end
end

--[刷数据]
function mt:init_data()
    if not self.has_ability then return end
    local handle = self:get_handle() 
    jass.SetUnitAbilityLevel( self.owner.handle, FourCC(self._id), 2 )
    japi.EXSetAbilityDataString( handle, 1, 0xD7, self.title_ui)
    japi.EXSetAbilityDataString( handle, 1, 0xDA, self.tip_ui)
    japi.EXSetAbilityDataString( handle, 1, 0xCC, self:get_art() )
    japi.EXSetAbilityDataReal( handle, 1, 0x69, self.cool )
    japi.EXSetAbilityDataReal( handle, 1, 0x6A, self.area ) 
    japi.EXSetAbilityDataReal( handle, 1, 0x6B, self.range )
    japi.EXSetAbilityDataReal( handle, 1, 0x6C, self.casttime)
    japi.EXSetAbilityDataInteger( handle, 1, 0x68, self.cost )
    japi.EXSetAbilityDataInteger( handle, 1, 0xC8, 0x201 ) 
    japi.EXSetAbilityDataReal( handle, 1, 0x6E, 1)
    japi.EXSetAbilityDataReal( handle, 1, 0x6D, 0)
    japi.EXSetAbilityDataReal( handle, 1, 0x70, self.caststiff and 1 or 0)
    if self:is_active() then 
        local hotkey = self:get_hotkey()
        japi.EXSetAbilityDataInteger( handle, 1, 0x64, flagcov.tobit(self.allowtype, skill.allow_flag) )
        japi.EXSetAbilityDataReal( handle, 1, 0x6D, flagcov.tobit(self.targettype, skill.target_flag) )
        if hotkey then 
            japi.EXSetAbilityDataInteger( handle, 1, 0xC8, message.keyboard[hotkey] or 0x201)
        end 
        japi.EXSetAbilityDataReal( handle, 1, 0x6E, self.is_selector and 3 or 1)
    end
    jass.SetUnitAbilityLevel( self.owner.handle, FourCC(self._id), 1 )
end 

skill.tooltips = {
    art = function(self)
        return self:get_art()
    end,
    title = function(self)
        return self:get_title()
    end,
    tip = function(self)
        return self:get_tip()
    end,
    message = function(self)
        return self.messagetip
    end,
    requires = function(self)
        return self.requires_ui
    end,
    description = function(self)
        return self.description
    end,
    enchant = function(self)
        return self.enchant_message
    end,
    resource = function(self)
        if self.requires_ui then 
            return 
        end 
        local cool = self:get_cool()
        local cost = self:get_cost()
        return {
            cool = math.max(math.floor(cool * 100) / 100, 0),
            cost = cost,
            gold = self:get_gold(),
            lumber = self.lum,
            food = self.food,
        }
    end,
}

-- 获取UI信息
function mt:get_tooltips(name)
    if name and skill.tooltips[name] then 
        return skill.tooltips[name](self)
    end 
end

function mt:atts_tip()
    if atts_tip then
        return atts_tip(self)
    end
    return ''
end


--[初始化]
local slots = {9,10,11,12,5,6,7,8,1,2,3,4}
local keys = {'Z','X','C','V','A','S','D','F','Q','W','E','R',}
function skill.init_skill(self)
    if not self.is_ability then return self end

    local unit = self.owner 
    local name = self.name 

    if not unit.used_slot_list  then 
        unit.used_slot_list = {}
    end 

    local slotid = self.slotid

    local has_ability = true
    if slotid and unit:is_type('商店')then
        slotid = slots[slotid]
    end
    
    if not slotid or unit.used_slot_list[slotid] then 
        has_ability = false
        for i = 1,12 do 
            slotid = i
            if unit:is_type('商店')then
                slotid = slots[i]
            end
            if not unit.used_slot_list[slotid] then 
                has_ability = true
                break 
            end
        end 
    end 
    

    if has_ability then 
        if not self.hotkey and unit:is_type('商店') then
            self.hotkey = keys[slotid]
        end
        unit.used_slot_list[slotid] = 1
        self.slotid = slotid
        self._id = table.remove(symbols[slotid], 1)
        self:add_ability()
        self:init_data()
    end 

    return self
end 

--加载模板数据
local function load_temp(data,temp)
    local temp_data = ac.skill[temp] or {}
    for k, v in pairs(temp_data) do
        if data[k] == nil then
            data[k] = v
        else
            data['temp_'..k] = v
        end
    end

    --追溯更早的模板
    local temp = temp_data.temp
    if temp then
        load_temp(data,temp)
    end
end

-- 单位添加技能
function ac.unit.__index:add_skill(name, skill_type, slotid, info)

    local data = ac.skill[name] 
    local skl = info or {}
    local base = ac.lni('skill',name) or {}
    skl.name = name 
    skl.owner = self
    skl.is_ability = skill_type=='英雄' or skl.is_ability
    skl.slotid = slotid or skl.slotid
    for k, v in pairs(data) do
        skl[k] = v
    end
    for k, v in pairs(base) do
        if skl[k] == nil then
            skl[k] = v
        end
    end
    --使用模板数据
    local temp = data.temp
    if temp then
        load_temp(skl,temp)
    end

    setmetatable(skl, data.prototype)
    skl:add(self)
    return skl 
end 

-- 单位添加技能列表
function ac.unit.__index:add_skills(data)
    local info = string.split(data,",")
    if #info[1] ~= 0 then 
        for i= 1,#info do
            self:add_skill(info[i])
        end  
    end 
end 

-- 单位删除所有技能
function ac.unit.__index:remove_skills()
    for skl in self:each_skill() do 
        skl:remove()
    end
end 

-- 单位遍历技能
function ac.unit.__index:each_skill()
	if not self.skills then
		return function () end
	end
    local tbl = {}
    for skl in pairs(self.skills) do
        table.insert(tbl, skl)
    end
	table.sort(tbl, function (a,b)
		return a.handle > b.handle
	end)
	local n = 0
	return function (t, v)
		n = n + 1
		return t[n]
	end, tbl
end 

-- 单位查找技能 同名技能返回最后添加的
function ac.unit.__index:find_skill(name)
    if not self.skills then 
        return 
    end
    local result 
    local handle = -1
    for skl in self:each_skill() do 
        if (skl.name == name or (skl.slotid==name and skl.has_ability)) and skl.handle > handle then 
            result = skl 
            handle = skl.handle
        end
    end 
    return result 
end 

-- 单位查找技能 根据命令
function ac.unit.__index:find_skill_by_order(order)
    if self.play_skills then 
        return self.play_skills[order]
    end
end 


local skill_cast_targets = {
    ["单位"] = function()
        return ac.unit.j_unit(jass.GetSpellTargetUnit())
    end,
    ["物品"] = function()
        return ac.item.j_item(jass.GetSpellTargetItem())
    end,
    ["可破坏物"] = function()
        return ac.destructable.j_destructable(jass.GetSpellTargetDestructable())
    end,
    ["点"] = function()
        return ac.point(jass.GetSpellTargetX(),jass.GetSpellTargetY())
    end,
}

-- 寻找技能释放目标
function skill.get_cast_target(name)
    if name and skill_cast_targets[name] then 
		return skill_cast_targets[name]() 
    end
end

local auto_targets = {
    ["物体"] = {"单位","物品","可破坏物",},
    ["点"] = {"点",},
    ["点&物体"] = {"单位","物品","可破坏物","点",},
}

-- 检索技能目标
local function auto_search_target(skl)
    if skl:is_active() and auto_targets[skl.targettype] then
        for k, v in ipairs(auto_targets[skl.targettype]) do 
            local target = skill.get_cast_target(v)
            if target then 
                return target
            end 
        end 
    end
end 

ac.message.sync:on "客户端-释放技能" (function (_, player, unit, head, tail)
    local syncid = (head) * 100 + (tail)
    local skl = skill.skillsmap[syncid]
    if skl and skl.targettype == "没有" and skl:cast_check() then 
        unit:set('魔法',unit:get('魔法') - skl:get_cost())
        skl._away_cooling = 0
        unit:notify("单位-发动技能效果", unit, skl)
        skl:notify("on_cast_shot")
        local away_cooling = skl._away_cooling
        skl._away_cooling = -1
        local cooldown = skl:get_max_cast_cd()
        if away_cooling == 1 then 
            cooldown = skl:get_cast_cd()
        end 
        skl:start_cd_by_cast(cooldown)
    end 
end)

ac.game:on "单位-本地命令" (function (t, unit, name, order, flags, ...)
    if unit.play_skills then 
        local order_skill = unit.play_skills[order]

        if order_skill then 
            if order_skill.is_shop_item then 
                local buyer = ac.player.self:get_store_target(unit)
                local ok,res = unit:dispatch('商店-购买商品检查',unit,order_skill,buyer)
                if ok then
                    ac.sync('同步-购买商品',unit.handle,order_skill.slotid)
                elseif res then
                    ac.console:error(ac.player.self, res)
                end
                return false
            end
            if order_skill:is_silent() then 
                return false 
            end 
            if order_skill:is_passive() then 
                return false 
            end 
            if (unit:has_restriction "沉默" or unit:has_restriction "禁魔") and not order_skill.physics then 
                return false
            end 
            if order_skill:notify("on_order_hook", flags , ...) == false then 
                return false 
            end
            if order_skill.instant_flag and not flags:find("瞬发") then 
                order_skill:client_cast()
                return false
            elseif order_skill.resume_flag and not flags:find("恢复") then 
                local flag = ac.message.convert_flag(flags .. "|恢复")
                local orderid = OrderId[order]
                if name == "无目标命令" then 
                    ac.message:order_call({"order_immediate", orderid, 0, flag,})
                elseif name == "点目标命令" then 
                    local x, y = ...
                    ac.message:order_call({"order_point", orderid, 0, x, y, flag,})
                elseif name == "指定目标命令" then 
                    local widget, x, y = ...
                    ac.message:order_call({"order_target", orderid, 0, x, y, widget, flag,})
                end 
                return false 
            end
        end 
    end 
end)

ac.game:on "单位-发布命令" (function (_, unit, orderid)
    local order = Order[orderid]
    if order and unit.play_skills then 
        local skl = unit.play_skills[order]
        if skl then 
            if ((not skl.physics and unit:has_restriction "沉默") or unit:has_restriction "禁魔") or (skl:is_silent()) then 
                unit:issue_order("move", unit:get_point())
                unit:issue_order("stop")
                return
            end 
        end 
    end
end )

ac.event.register("任意单位-准备释放技能", function()
    local unit = ac.unit(jass.GetTriggerUnit())
    if unit and unit.play_skills then 
        local abilid = jass.GetSpellAbilityId()
        local skl = unit.play_skills[abilid]
        if skl then 
            if ((not skl.physics and unit:has_restriction "沉默") or unit:has_restriction "禁魔")or (skl:is_silent()) then 
                unit:issue_order("move", unit:get_point())
                unit:issue_order("stop")
                return
            end 
            skl.current_step = "cast_ready"
            local target = auto_search_target(skl)
            unit:notify("单位-准备释放技能", unit, skl, target)
            skl:notify("on_cast_ready", target)
        end 
    end 
end)

ac.event.register("任意单位-开始释放技能", function()
    local unit = ac.unit(jass.GetTriggerUnit())
    if unit and unit.play_skills then 
        local abilid = jass.GetSpellAbilityId()
        local skl = unit.play_skills[abilid]
        if skl and skl.current_step == "cast_ready" then 
            if ((not skl.physics and unit:has_restriction "沉默") or unit:has_restriction "禁魔") or (skl:is_silent()) then 
                unit:issue_order("move", unit:get_point())
                unit:issue_order("stop")
                return
            end 
            if skl.animation and skl.casttime > 0 then 
                skl.animation_timer = unit:wait(0, function()
                    unit:play_animation (skl.animation )
                end)
            end 
            skl.current_step = "cast_start"
            local target = auto_search_target(skl)
            unit:notify("单位-开始释放技能", unit, skl, target)
            skl:notify("on_cast_start", target)
        end 
    end 
end)

ac.event.register("任意单位-发动技能效果", function()
    local unit = ac.unit(jass.GetTriggerUnit())
    if unit and unit.play_skills then 
        local abilid = jass.GetSpellAbilityId()
        local skl = unit.play_skills[abilid]
        if skl  then 
            if skl.current_step == "cast_start" then 
                skl.current_step = "cast_shot"
                unit:set('魔法',unit:get('魔法') - skl:get_cost())
                local target = auto_search_target(skl)
                skl._away_cooling = 0
                unit:notify("单位-发动技能效果", unit, skl, target)
                skl.target = target
                skl:notify("on_cast_shot", target)
                local away_cooling = skl._away_cooling
                skl._away_cooling = -1
                local cooldown = skl:get_max_cast_cd()
                if away_cooling == 1 then 
                    cooldown = skl:get_cast_cd()
                end 
                unit:wait(0 , function()
                    skl:start_cd_by_cast(cooldown)
                end)
            elseif skl.current_step == "cast_stop" then 
                if skl.animation_timer then 
                    skl.animation_timer:remove()
                    skl.animation_timer = nil 
                end 
                unit:play_animation "stand"
                unit:wait(0 , function()
                    japi.EXSetAbilityState(skl:get_handle(), 0x01, 0.01)
                end)
            end
        end 
    end 
end)

ac.event.register("任意单位-释放技能结束", function()
    local unit = ac.unit(jass.GetTriggerUnit())
    if unit and unit.play_skills then 
        local abilid = jass.GetSpellAbilityId()
        local skl = unit.play_skills[abilid]
        if skl and skl.current_step == "cast_shot" then 
            skl.current_step = "cast_finish"
            local target = auto_search_target(skl)
            unit:notify("单位-释放技能结束", unit, skl, target)
            skl:notify("on_cast_finish", target)
        end 
    end 
end)

ac.event.register("任意单位-停止释放技能", function()
    local unit = ac.unit(jass.GetTriggerUnit())
    if unit and unit.play_skills then 
        local abilid = jass.GetSpellAbilityId()
        local skl = unit.play_skills[abilid]
        if skl then 
            skl.current_step = "cast_stop"
            local target = auto_search_target(skl)
            unit:notify("单位-停止释放技能", unit, skl, target)
            skl:notify("on_cast_stop", target)
            unit:play_animation "stand"
            if skl.animation_timer then 
                skl.animation_timer:remove()
                skl.animation_timer = nil 
            end 
        end 
    end 
end)


---@type table<string, ac.skill>
ac.skill = setmetatable({}, {
    __index = function(self, name)
        local mt = {}
        self[name] = mt 
        mt.name = name 
        mt.prototype = {
            __index = mt,
            __tostring = skill.__tostring,
        } 
        local data = ac.lni("skill", name)
		if data then 
            data.__index = data
            data.__tostring = skill.__tostring	
			data.__call = skill.__call 
            setmetatable(data, skill)
			setmetatable(mt, data)
		else 
			setmetatable(mt, skill)
		end 
        return mt 
    end,
})



return skill 